#include <linux/tty.h>

#include <asm/io.h>
#include <asm/system.h>

unsigned long video_num_columns;      // 显示列数
unsigned long video_num_lines;        // 显示行数

static unsigned char video_type;      // 显示模式
static unsigned long video_mem_base;  // 显存起始地址
static unsigned long video_mem_term;  // 显存结束地址
static unsigned long video_size_row;  // 每行字节数
static unsigned char video_page;      // 初始页面
static unsigned short video_port_reg; // 显示控制器索引寄存器端口
static unsigned short video_port_val; // 显示控制器数据寄存器端口
static int can_do_color = 0;          // 是否可以彩色显示

static struct {
    unsigned short vc_video_erase_char; // 背景擦除字符
    unsigned char vc_attr;              // 当前字符的属性（颜色，亮度等）
    unsigned long vc_origin;            // 用于EGA/VGA快速滚动
    unsigned long vc_scr_end;           // EGA/VGA快速滚动的结束位置
    unsigned long vc_pos;               // 当前字符位置
    unsigned long vc_x, vc_y;           // 光标所在位置
    unsigned long vc_top, vc_bottom;    // 滚动位置
    unsigned long vc_video_mem_start;   // 视频RAM开始地址
    unsigned long vc_video_mem_end;     // 视频RAM结束地址
    /* misc */
    unsigned long vc_need_wrap : 1;
} vc_cons;

#define origin           (vc_cons.vc_origin)
#define scr_end          (vc_cons.vc_scr_end)
#define pos              (vc_cons.vc_pos)
#define top              (vc_cons.vc_top)
#define bottom           (vc_cons.vc_bottom)
#define x                (vc_cons.vc_x)
#define y                (vc_cons.vc_y)
#define attr             (vc_cons.vc_attr)
#define video_mem_start  (vc_cons.vc_video_mem_start)
#define video_mem_end    (vc_cons.vc_video_mem_end)
#define video_erase_char (vc_cons.vc_video_erase_char)
#define need_wrap        (vc_cons.vc_need_wrap)

static void gotoxy(int new_x, int new_y) {
    int max_y;

    if (new_x < 0)
        x = 0;
    else if (new_x >= video_num_columns)
        x = video_num_columns - 1;
    else
        x = new_x;
    max_y = video_num_lines;
    if (new_y < 0)
        y = 0;
    else if (new_y >= max_y)
        y = max_y - 1;
    else
        y = new_y;
    pos = origin + y * video_size_row + (x << 1);
}

static unsigned short __real_origin;
static unsigned short __origin;

static inline void __set_origin(unsigned short offset) {
    unsigned long flags;
    save_flags(flags);
    cli();
    __origin = offset;
    outb_p(12, video_port_reg);
    outb_p(offset >> 8, video_port_val);
    outb_p(13, video_port_reg);
    outb_p(offset, video_port_val);
    restore_flags(flags);
}

static void set_origin() {
    if (video_type != VIDEO_TYPE_EGAC && video_type != VIDEO_TYPE_EGAM)
        return;
    __real_origin = (origin - video_mem_base) >> 1;
    __set_origin(__real_origin);
}

static inline void set_cursor() {
    unsigned long flags;

    if (__real_origin != __origin)
        set_origin(__real_origin);
    save_flags(flags);
    cli();
    outb_p(14, video_port_reg);
    outb_p(0xff & ((pos - video_mem_base) >> 9), video_port_val);
    outb_p(15, video_port_reg);
    outb_p(0xff & ((pos - video_mem_base) >> 1), video_port_val);
    restore_flags(flags);
}

static void scrup(unsigned int t, unsigned int b) {
    int hardscroll = 1;

    if (b > video_num_lines || t >= b)
        return;
    if (video_type != VIDEO_TYPE_EGAC && video_type != VIDEO_TYPE_EGAM)
        hardscroll = 0;
    else if (t || b != video_num_lines)
        hardscroll = 0;
    if (hardscroll) {
        origin += video_size_row;
        pos += video_size_row;
        scr_end += video_size_row;
        if (scr_end > video_mem_end) {
            __asm__("cld\n\t"
                    "rep\n\t"
                    "movsl\n\t"
                    "movl %2, %1\n\t"
                    "rep\n\t"
                    "stosw"
                    : /* no output */
                    : "a"(video_erase_char),
                      "c"((video_num_lines - 1) * video_num_columns >> 1),
                      "m"(video_num_columns),
                      "D"(video_mem_start),
                      "S"(origin));
            scr_end -= origin - video_mem_start;
            pos -= origin - video_mem_start;
            origin = video_mem_start;
        } else {
            __asm__("cld\n\t"
                    "rep\n\t"
                    "stosw"
                    : /* no output */
                    : "a"(video_erase_char),
                      "c"(video_num_columns),
                      "D"(scr_end - video_size_row));
        }
        set_origin();
    } else {
        __asm__("cld\n\t"
                "rep\n\t"
                "movsl\n\t"
                "movl %2, %%ecx\n\t"
                "rep\n\t"
                "stosw"
                : /* no output */
                : "a"(video_erase_char),
                  "c"((b - t - 1) * video_num_columns >> 1),
                  "m"(video_num_columns),
                  "D"(origin + video_size_row * t),
                  "S"(origin + video_size_row * (t + 1)));
    }
}

static void lf() {
    if (y + 1 < bottom) {
        y++;
        pos += video_size_row;
        return;
    } else
        scrup(top, bottom);
    need_wrap = 0;
}

static inline void cr() {
    pos -= x << 1;
    need_wrap = x = 0;
}

void console_print(const char *b) {
    unsigned char c;

    while ((c = *(b++)) != 0) {
        if (c == 10 || c == 13 || need_wrap) {
            if (c != 13)
                lf();
            cr();
            if (c == 10 || c == 13)
                continue;
        }
        *(unsigned short *)pos = (attr << 8) + c;
        if (x == video_num_columns - 1) {
            need_wrap = 1;
            continue;
        }
        x++;
        pos += 2;
    }
    set_cursor();
}

void con_init() {
    char *display_desc = "????";
    char *display_ptr;
    int orig_x = ORIG_X;
    int orig_y = ORIG_Y;

    video_num_columns = ORIG_VIDEO_COLS;    // 列数
    video_size_row = video_num_columns * 2; // 每行字符数
    video_num_lines = ORIG_VIDEO_LINES;     // 行数
    video_page = ORIG_VIDEO_PAGE;           // 页号

    if (ORIG_VIDEO_MODE == 7) {             // 单色显示
        video_mem_base = 0xb0000;
        video_port_reg = 0x3b4;
        video_port_val = 0x3b5;
        if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10) {
            video_type = VIDEO_TYPE_EGAM;
            video_mem_term = 0xb8000;
            display_desc = "EGA+";
        } else {
            video_type = VIDEO_TYPE_MDA;
            video_mem_term = 0xb2000;
            display_desc = "*MDA";
        }
    } else { // 彩色显示
        can_do_color = 1;
        video_mem_base = 0xb8000;
        video_port_reg = 0x3d4;
        video_port_val = 0x3d5;
        if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10) {
            video_type = VIDEO_TYPE_EGAC;
            video_mem_term = 0xc0000;
            display_desc = "EGA+";
        } else {
            video_type = VIDEO_TYPE_CGA;
            video_mem_term = 0xba000;
            display_desc = "*CGA";
        }
    }

    attr = 0x07;
    video_erase_char = (attr << 8) | ' ';
    need_wrap = 0;
    top = 0;
    bottom = video_num_lines;

    video_mem_start = video_mem_base;
    video_mem_end = video_mem_term;
    origin = video_mem_start;
    scr_end = video_mem_start + video_num_lines * video_size_row;

    gotoxy(orig_x, orig_y);

    // 打印正在使用的显示驱动程序
    display_ptr = ((char *)video_mem_base) + video_size_row - 8;
    while (*display_desc) {
        *display_ptr++ = *display_desc++;
        display_ptr++;
    }
}
